package com.test.redis.service;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

/**
 * 基于Redis Bitmap的会议室预定服务
 * 使用Bitmap记录每个会议室每分钟的预定状态，支持快速查询和冲突检测
 */
@Service
@Slf4j
public class ReservationBitMapService {

    private final RedisTemplate<String, Object> redisTemplate;
    private static final String RESERVATION_BITMAP_KEY = "reservation:room:%d:date:%s";

    public ReservationBitMapService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 检查时间段是否可用
     */
    public boolean isTimeSlotAvailable(Long roomId, LocalDateTime startTime, LocalDateTime endTime) {
        String dateStr = startTime.toLocalDate().toString();
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, dateStr);

        int startMinute = timeToMinuteOffset(startTime);
        int endMinute = timeToMinuteOffset(endTime);

        // 检查时间段内是否有已被预定的位
        for (int i = startMinute; i < endMinute; i++) {
            Boolean isBooked = redisTemplate.opsForValue().getBit(key, i);
            if (Boolean.TRUE.equals(isBooked)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 预定时间段
     */
    public boolean reserveTimeSlot(Long roomId, LocalDateTime startTime, LocalDateTime endTime, String reservationId) {
        String dateStr = startTime.toLocalDate().toString();
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, dateStr);

        int startMinute = timeToMinuteOffset(startTime);
        int endMinute = timeToMinuteOffset(endTime);

        // 使用Redis事务确保原子性操作
        redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) {
                operations.multi();

                // 首先检查是否所有位都可用
                for (int i = startMinute; i < endMinute; i++) {
                    operations.opsForValue().getBit(key, i);
                }

                List<Object> results = operations.exec();
                if (results != null) {
                    // 检查是否有已被预定的位
                    for (Object result : results) {
                        if (Boolean.TRUE.equals(result)) {
                            return false;
                        }
                    }
                }

                // 设置位图
                operations.multi();
                for (int i = startMinute; i < endMinute; i++) {
                    operations.opsForValue().setBit(key, i, true);
                }
                operations.exec();

                return true;
            }
        });

        return true;
    }

    /**
     * 取消预定
     */
    public void cancelTimeSlot(Long roomId, LocalDateTime startTime, LocalDateTime endTime) {
        String dateStr = startTime.toLocalDate().toString();
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, dateStr);

        int startMinute = timeToMinuteOffset(startTime);
        int endMinute = timeToMinuteOffset(endTime);

        // 清除位图
        for (int i = startMinute; i < endMinute; i++) {
            redisTemplate.opsForValue().setBit(key, i, false);
        }
    }

    /**
     * 将时间转换为当天分钟的偏移量
     */
    private int timeToMinuteOffset(LocalDateTime dateTime) {
        return dateTime.getHour() * 60 + dateTime.getMinute();
    }

    /**
     * 获取会议室某天的预定情况
     */
    public List<TimeSlot> getDailyReservations(Long roomId, LocalDate date) {
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, date.toString());
        List<TimeSlot> timeSlots = new ArrayList<>();

        RedisCallback<byte[]> redisCallback = connection -> connection.get(key.getBytes());
        BitSet bitSet;
        byte[] bitmapBytes =  redisTemplate.execute(redisCallback);
        if (bitmapBytes != null) {
            bitSet = BitSet.valueOf(bitmapBytes);
        } else {
            // 一天1440分钟
            bitSet = new BitSet(24 * 60);
        }

        // 将连续的预定时间段合并
        int start = -1;
        for (int i = 0; i < 24 * 60; i++) {
            boolean isBooked = bitSet.get(i);
            if (isBooked && start == -1) {
                start = i;
            } else if (!isBooked && start != -1) {
                timeSlots.add(new TimeSlot(
                    minuteToTime(start, date),
                    minuteToTime(i, date)
                ));
                start = -1;
            }
        }

        if (start != -1) {
            timeSlots.add(new TimeSlot(
                minuteToTime(start, date),
                minuteToTime(24 * 60, date)
            ));
        }

        return timeSlots;
    }

    private LocalDateTime minuteToTime(int minute, LocalDate date) {
        int hours = minute / 60;
        int minutes = minute % 60;
        return LocalDateTime.of(date, LocalTime.of(hours, minutes));
    }

    @Data
    @AllArgsConstructor
    public static class TimeSlot {
        private LocalDateTime startTime;
        private LocalDateTime endTime;
    }
}